/*
 * =====================================================================================
 *
 *       Filename:  cl_assert.hxx
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  11/28/2011 02:23:17 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Ken Chen (Ken), chen_kenz@emc.com
 *        Company:  EMC
 *
 * =====================================================================================
 */

#ifndef _CL_ASSERT_HXX_
#define _CL_ASSERT_HXX_

#include <cstdlib>
#include <sstream>
#include <iostream>
#include <string>
#include <functional>
#include <map>

// An implementaion from Andrei Alexandrescu
namespace neo
{

enum
{
  LVL_WARN = 100,
  LVL_DEBUG = 200,
  LVL_ERROR = 300,
  LVL_ENFORCE = 301,
  LVL_FATAL = 1000
};

class CLAsserterContext
{
private:
  typedef std::map<std::string, std::string> ContextCollection;

public:
  CLAsserterContext()
    : m_strExpr(""),
    m_valExpr(""),
    m_level(LVL_DEBUG)
  {
  }

  void setExpr(const std::string& valExpr, const std::string& strExpr)
  {
    m_valExpr = valExpr;
    m_strExpr = strExpr;
  }

  void addContext(const std::string& key, const std::string& val)
  {
    m_contxt.insert(std::make_pair(key, val));
  }

  void setLevel(int lvl)
  {
    m_level = lvl;
  }

  int getLevel(void) const
  {
    return m_level;
  }

  void setMsg(const std::string& msg)
  {
    m_msg = msg;
  }

  std::string getLine(void) const
  {
    return getVal("line");
  }

  std::string getFile(void) const
  {
    return getVal("file");
  }

  std::string getContextStr(void) const
  {
    std::string contxt;
    contxt.reserve(100);
    contxt.append("Assertion failed at file: ");
    contxt.append(getFile());
    contxt.append(", line: ");
    contxt.append(getLine());
    contxt.append(" \n");
    contxt.append(m_msg);
    contxt.append(" \n");
    contxt.append(m_strExpr);
    contxt.append(": ");
    contxt.append(m_valExpr);
    contxt.append(" \n");

    ContextCollection::const_iterator itr = m_contxt.begin();
    ContextCollection::const_iterator itrEnd = m_contxt.end();
    for(; itr != itrEnd; ++itr)
    {
      if((itr->first != "line") && (itr->first != "file"))
      {
        contxt.append(itr->first);
        contxt.append(": ");
        contxt.append(itr->second);
        contxt.append(" \n");
      }
    }
    return contxt;
  }

private:
  std::string getVal(const std::string& key) const
  {
    ContextCollection::const_iterator itr = m_contxt.find(key);
    if(itr != m_contxt.end())
    {
      return itr->second;
    }
    else
    {
      return std::string("");
    }
  }

private:
  std::string m_strExpr;
  std::string m_valExpr;
  std::string m_msg;
  int m_level;
  ContextCollection m_contxt;
};

typedef std::function<void(const CLAsserterContext&)> CLAsserterHandler;
typedef std::function<void(const std::string&)> CLAsserterLogger;

void dumpAssertionToConsole(const std::string& msg)
{
  std::cerr << msg;
}

#define BREAK_INTO_DEBUGGER \
  do{ \
    __asm ("int $0x3"); \
  }while(0)

void handleAssertion(const CLAsserterContext& contxt)
{
  if(contxt.getLevel() >= LVL_ERROR)
  {
    abort();
  }
  else if(contxt.getLevel() == LVL_DEBUG)
  {
    BREAK_INTO_DEBUGGER;
  }
  else
  {
    // throw 
  }
}

class CLAsserterSettings
{
public:
  CLAsserterSettings()
    : m_logger(CLAsserterLogger(dumpAssertionToConsole)),
    m_handler(CLAsserterHandler(handleAssertion))
  {
  }

  void log(const CLAsserterContext& contxt)
  {
    if(m_logger)
    {
      m_logger(contxt.getContextStr());
    }
  }

  void handle(const CLAsserterContext& contxt)
  {
    if(m_handler)
    {
      m_handler(contxt);
    }
  }

  void setLogger(const CLAsserterLogger& logger)
  {
    m_logger = logger;
  }

  void setHandler(const CLAsserterHandler& handler)
  {
    m_handler = handler;
  }

private:
  CLAsserterLogger m_logger;
  CLAsserterHandler m_handler;
};

CLAsserterSettings* clAsserterSetting(void)
{
  static CLAsserterSettings asserterSettings;
  return &asserterSettings;
}

#undef ASSERTER_A
#undef ASSERTER_B
#undef ASSERTER_OP

class CLAsserter
{
public:
  CLAsserter()
    : ASSERTER_A(*this),
    ASSERTER_B(*this)
  {
    m_handler = clAsserterSetting();
  }
  
  CLAsserter(const CLAsserter& other)
    : ASSERTER_A(*this),
    ASSERTER_B(*this),
    m_handler(other.m_handler),
    m_context(other.m_context) 
  {
  }


  ~CLAsserter()
  {
    handle();
  }

  static CLAsserter create(void)
  {
    return CLAsserter();
  }

public:
  template<typename T>
  CLAsserter& context(const char* key, const T& val)
  {
    // 'val' should support serialized thr ostringstream
    // This is a hard restriction - TODO...
    std::ostringstream os;
    os << val;
    m_context.addContext(key, os.str()); 
    return *this;
  }

  template<typename T>
  CLAsserter& context(const T& key, const char* val)
  {
    // 'key' should support serialized thr ostringstream
    // This is a hard restriction - TODO...
    std::ostringstream os;
    os << key;
    m_context.addContext(os.str(), val); 
    return *this;
  }

  CLAsserter& context(const char* key, const char* val)
  {
    m_context.addContext(key, val); 
    return *this;
  }

  CLAsserter& debug(const char* msg = "")
  {
    m_context.setLevel(LVL_DEBUG);
    m_context.setMsg(msg);
    return *this;
  }

  CLAsserter& warn(const char* msg = "")
  {
    m_context.setLevel(LVL_WARN);
    m_context.setMsg(msg);
    return *this;
  }

  CLAsserter& error(const char* msg = "")
  {
    m_context.setLevel(LVL_ERROR);
    m_context.setMsg(msg);
    return *this;
  }

  CLAsserter& fatal(const char* msg = "")
  {
    m_context.setLevel(LVL_FATAL);
    m_context.setMsg(msg);
    return *this;
  }

  template<typename T>
  CLAsserter& testExpr(T val, const char* expr)
  {
    // 'val' should support serialized thr ostringstream
    // This is a hard restriction - TODO...
    std::ostringstream os;
    os << val;
    m_context.setExpr(os.str(), expr);
    return *this;
  }

public:
  CLAsserter &ASSERTER_A;
  CLAsserter &ASSERTER_B;

private:
  void handle(void)
  {
    try{
      m_handler->log(m_context);
      m_handler->handle(m_context);
    }catch(const std::exception& ex){
      // don't let the exception flee out of dctor
    }catch(...){
    }
  }

private:
  // m_handler is just a visitor, it does not own the resource
  CLAsserterSettings *m_handler;
  CLAsserterContext m_context;
};


#ifdef NDEBUG
#define ASSERT_IT false 
#else
#define ASSERT_IT true 
#endif

#define ASSERT(expr) \
  if(expr); else \
    if(ASSERT_IT) \
      CLAsserter::create().testExpr((expr), #expr).context("file", __FILE__).context("line", __LINE__).ASSERTER_A

#define ASSERTER_A(x) ASSERTER_OP(x, B)
#define ASSERTER_B(x) ASSERTER_OP(x, A)

#define ASSERTER_OP(x, next) \
  ASSERTER_A.context((x), #x).ASSERTER_## next

} // neo

#endif //_CL_ASSERT_HXX_

